


#include "qe_rollingfile.h"
#include "qe_memory.h"
#include "qe_string.h"
#include "qe_list.h"
#include "qe_log.h"
#include "qe_time.h"
#include "qe_assert.h"



#if defined(HAVE_DIRECT_H)
#include <dirent.h>
#else
#error "System dont't have dirent.h"
#endif



QELOG_DOMAIN(QELOG_DOMAIN_ROLLFILE);



#define QE_ROLLFILE_BACKUP_FORMAT   "%04d%02d%02d%02d%02d%02d.%s"



#define QE_ROLLFILE_HOOK(rf, hook, data)  do {\
    if ((rf)->hooks[hook])\
        (rf)->hooks[hook](data);\
} while(0)


typedef struct
{
    char *name;
    qe_list list;
} rf_backup_t;

static qe_bool check_file_exist(const char *name)
{
    FILE *fp = fopen(name, "r");
    if (!fp)
        return qe_false;
    fclose(fp);
    return qe_true;
}

static void backup_free(rf_backup_t *backup)
{
    if (!backup)
        return;

    if (backup->name)
        qe_free(backup->name);

    qe_free(backup);
}

static void backup_remove(qe_rollingfile *rf, rf_backup_t *backup)
{
    QE_ROLLFILE_HOOK(rf, QE_ROLLINGFILE_HOOK_BEFORE_REMOVE, backup->name);
    remove(backup->name);
    qe_debug("remove backup %s, total:%d", backup->name, rf->count);
    backup_free(backup);
}

static void backup_del_by_name(qe_rollingfile *rf, const char *name)
{
    rf_backup_t *backup, *tmp;

    qe_list_foreach_entry_safe(backup, tmp, &rf->rollings, list) {
        if (qe_strcmp(backup->name, name) == 0) {
            qe_list_remove(&backup->list);
            rf->count--;
            backup_remove(rf, backup);
        }
    }
}

static void backup_del_oldest(qe_rollingfile *rf)
{
    rf_backup_t *backup = qe_list_first_entry(&rf->rollings, rf_backup_t, list);
    qe_list_remove(&backup->list);
    rf->count--;
    backup_remove(rf, backup);
}

static qe_bool backup_name_match(char *filename, qe_rollingfile *rf)
{
    int mr;
    char name[128];
    char extension[16];
    qe_date_t date;
    
    mr = sscanf(filename, "%49[^-]-"QE_ROLLFILE_BACKUP_FORMAT,
        name, &date.year, &date.mon, &date.day, &date.hour, &date.min, &date.sec, 
        extension);

    if ((mr == 8) && (qe_strcmp(name, rf->name) == 0)) {
        return qe_true;
    }

    return qe_false;
}

static qe_ret backup_add_by_name(qe_rollingfile *rf, char *name)
{
    rf_backup_t *back = qe_malloc(sizeof(rf_backup_t));
    qe_assert(back != QE_NULL);
    back->name = qe_strdup_format("%s/%s", rf->dir, name);
    qe_assert(back->name != QE_NULL);
    qe_list_append(&back->list, &rf->rollings);
    rf->count++;
    qe_debug("add backup:%s, total:%d", name, rf->count);
    return qe_ok;
}

static qe_ret backup_add(qe_rollingfile *rf)
{
    qe_date_t date;
    char *name;

    rf_backup_t *back = qe_malloc(sizeof(rf_backup_t));
    if (!back)
        return qe_err_mem;

    qe_date(&date);

    name = qe_strdup_format("%s/%s-"QE_ROLLFILE_BACKUP_FORMAT,
        rf->dir, rf->name, date.year, date.mon, date.day, date.hour, 
        date.min, date.sec, rf->extension);

    if (check_file_exist(name)) {
        backup_del_by_name(rf, name);
    }

    back->name = name;

    /* rename current file */
    rename(rf->filename, back->name);
    
    /* add to rollings */
    qe_list_append(&back->list, &rf->rollings);
    rf->count++;

    qe_debug("add backup:%s, total:%d\n", back->name, rf->count);

    return qe_ok;
}

static qe_ret history_scan(qe_rollingfile *rf)
{
    DIR *dir;
    struct dirent* entry;

    dir = opendir(rf->dir);
    if (!dir) {
        qe_error("opendir %s error", rf->dir);
        return qe_err_param;
    }

    while ((entry = readdir(dir)) != QE_NULL) {
        /* skip current dir and parent */
        if (qe_strcmp(entry->d_name, ".") == 0 || 
            qe_strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        if (backup_name_match(entry->d_name, rf)) {
            backup_add_by_name(rf, entry->d_name);
        }
    }

    closedir(dir);

    return qe_ok;
}

static qe_ret check_rolling(qe_rollingfile *rf)
{
    qe_ret ret;

    if (rf->fp != QE_NULL) {

        if (ftell(rf->fp) >= rf->max_size) {
            /* close current file */
            fclose(rf->fp);
            rf->fp = NULL;
            ret = backup_add(rf);
            if (ret != qe_ok) {
                qe_error("backup add error:%d", ret);
                return ret;
            }
            
            if (rf->count > rf->num_roll) {
                backup_del_oldest(rf);
            }
        }
    }

    if (rf->fp == QE_NULL) {
        rf->fp = fopen(rf->filename, "w");
        if (!rf->fp) {
            qe_error("open %s error", rf->filename);
            return qe_err_io;
        }
    }

    return qe_ok;
}

qe_rollingfile *qe_rollingfile_new(const char *dir, const char *name, 
    const char *extension, qe_u32 num_roll, qe_size max_size)
{
    int i;
    qe_ret ret;

    if (!dir || !name || !extension || !max_size) {
        qe_error("invalid param");
        return QE_NULL;
    }

    qe_rollingfile *rf = qe_malloc(sizeof(qe_rollingfile));
    if (!rf)
        return QE_NULL;
    rf->max_size  = max_size;
    rf->num_roll  = num_roll;
    rf->dir       = qe_strdup(dir);
    rf->name      = qe_strdup(name);
    rf->extension = qe_strdup(extension);
    rf->fp        = QE_NULL;
    rf->count     = 0;
    rf->filename  = qe_strdup_format("%s/%s.%s", dir, name, extension);

    for (i=0; i<QE_ROLLINGFILE_HOOK_SIZEOF; i++) {
        rf->hooks[i] = QE_NULL;
    }

    qe_list_init(&rf->rollings);

    qe_debug("history scan\n");
    ret = history_scan(rf);
    if (ret != qe_ok) {
        qe_error("history scan error:%d", ret);
        qe_rollingfile_free(rf);
        return QE_NULL;
    }

    return rf;
}

qe_ret qe_rollingfile_append_buffer(qe_rollingfile *rf, const void *buf, qe_size size)
{
    qe_ret ret;

    if (!rf || !buf) {
        qe_error("invalid param");
        return qe_err_param;
    }

    ret = check_rolling(rf);
    if (ret != qe_ok) {
        qe_error("check rolling error:%d", ret);
        return ret;
    }

    fwrite(buf, size, 1, rf->fp);
    fflush(rf->fp);

    return qe_ok;
}

qe_ret qe_rollingfile_append_string(qe_rollingfile *rf, const char *string)
{
    qe_ret ret;

    if (!rf || !string) {
        qe_error("invalid param");
        return qe_err_param;
    }

    ret = check_rolling(rf);
    if (ret != qe_ok) {
        qe_error("check rolling error:%d", ret);
        return ret;
    }

    fprintf(rf->fp, "%s", string);
    fflush(rf->fp);

    return qe_ok;
}

void qe_rollingfile_free(qe_rollingfile *rf)
{
    qe_list *node, *tmp;
    rf_backup_t *back;

    if (rf->fp) {
        fclose(rf->fp);
    }

    qe_list_foreach_safe(node, tmp, &rf->rollings) {
        back = qe_list_entry(node, rf_backup_t, list);
        qe_list_remove(&back->list);
        qe_free(back->name);
        qe_free(back);
    }

    qe_free(rf->dir);
    qe_free(rf->name);
    qe_free(rf->extension);
    qe_free(rf);
    rf = QE_NULL;
}

void qe_rollingfile_set_hook(qe_rollingfile *rf, qe_rollingfile_hook_e type, 
    qe_rollingfile_hook_t hook)
{
    if (!rf) {
        return;
    }

    if (type >= QE_ROLLINGFILE_HOOK_SIZEOF)
        return;

    rf->hooks[type] = hook;
}